package org.flexthinker.binding.value.support
{
    import mx.collections.IList;

    import mx.events.CollectionEvent;

    import org.flexthinker.binding.value.PropertyChangeListener;
    import org.flexthinker.binding.value.PropertyChangePublisher;

    import flash.events.Event;

    import mx.binding.utils.ChangeWatcher;
    import mx.events.PropertyChangeEvent;

    /**
     *  Wrapper for ChangeWatacher calling
     * <code>PropertyChangeListener.propertyChange(PropertyChangeEvent)</code> method
     *  when <code>host[propertyName]</code> value changes.
     *
     * <p>
     *     Specific to this class is that it always calls listener w/ PropertyChangeEvent,
     *     while ChangeWatcher doesn't do that always due to Bindings with custom events.
     * </p>
     *
     * <p>
     *     This class watches when the <code>propertyName</code> has changed and
     *     if the property is <code>IList</code>, it also watches for
     *     <code>CollectionEvent.COLLECTION_CHANGE</code> event.
     * </p>
     *
     *
     */
    public class PropertyChangePublisherImpl implements PropertyChangePublisher
    {
        private var changeWatcher:ChangeWatcher;

        protected var host:Object;

        protected var propertyName:String;

        private var listeners:Vector.<PropertyChangeListener> = new Vector.<PropertyChangeListener>();


        public function PropertyChangePublisherImpl(host:Object, propertyName:String)
        {
            this.host = host;
            this.propertyName = propertyName;
        }

        /**
         *  Sets a new <code>host</code> Object.
         *  The host object must have a public property specified by the <code>propertyName</code> parameter.
         * @param newHost
         */
        public function setHost( newHost:Object ):void
        {
            if ( newHost == null )
            {
                throw new ArgumentError("Please provide a non-null value");
            }

            if ( newHost == host )
            {
                return;
            }

            var oldValue:Object = host ? host[propertyName] : null;

            unWatch();

            host = newHost;

            setChangeWatcher();
            watchListChanges();

            var event:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE,false,false, null,
                                                    propertyName, oldValue, host[propertyName], host);
            notifyListeners(event);
        }

        public function getHost():Object
        {
            return host;
        }

        public function addPropertyChangeListener(listener:PropertyChangeListener):void
        {
            if (listener == null || host == null || propertyName == null)
            {
                return;
            }

            listeners.push(listener);

            setChangeWatcher();
            watchListChanges();
        }

        public function removePropertyChangeListener(listener:PropertyChangeListener):void
        {
            var idx:int = listeners.indexOf(listener);
            if (idx >= 0)
            {
                listeners.splice(idx, 1);
            }
            if (listeners.length == 0)
            {
                unWatch();
            }
        }

        public function removeAllListeners():void
        {
            listeners = new Vector.<PropertyChangeListener>();
            unWatch();
        }

        public function get isListening():Boolean
        {
            return ( changeWatcher != null && changeWatcher.isWatching() )
        }

        private function setChangeWatcher():void
        {
            if (changeWatcher == null)
            {
                changeWatcher = ChangeWatcher.watch(host, propertyName, propertyChangedHandler, false, false);
            }
        }

        private function watchListChanges():void
        {
            if (host && propertyName && host[propertyName] is IList &&
                    !(IList(host[propertyName]).hasEventListener(CollectionEvent.COLLECTION_CHANGE)))
            {
                IList(host[propertyName]).addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
            }
        }

        /**
         * Called when all listeners have been removed
         */
        private function unWatch():void
        {
            if (changeWatcher)
            {
                changeWatcher.unwatch();
                changeWatcher = null;
                if (host && propertyName && host[propertyName] is IList)
                {
                    IList(host[propertyName]).removeEventListener(CollectionEvent.COLLECTION_CHANGE,
                            collectionChangeHandler);
                }
            }
        }

        private function propertyChangedHandler(event:Event):void
        {
            var newEvt:PropertyChangeEvent = (event is PropertyChangeEvent ? PropertyChangeEvent(event) :
                    new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE, false, false, null,
                            propertyName, null, host[propertyName], host));

            watchListChanges();

            notifyListeners(newEvt);
        }

        private function collectionChangeHandler(event:CollectionEvent):void
        {
            var newEvt:PropertyChangeEvent = new PropertyChangeEvent(CollectionEvent.COLLECTION_CHANGE, false, false,
                    null,
                    propertyName, null, host[propertyName], host);
            notifyListeners(newEvt);
        }

        private function notifyListeners(event:PropertyChangeEvent):void
        {
            for each (var listener:PropertyChangeListener in listeners)
            {
                listener.propertyChange(event);
            }
        }

    }
}